home *** CD-ROM | disk | FTP | other *** search
/ Night Owl 6 / Night Owl's Shareware - PDSI-006 - Night Owl Corp (1990).iso / 028a / dezip20.zip / DEZIP.PAS < prev    next >
Pascal/Delphi Source File  |  1989-07-31  |  60KB  |  1,685 lines

  1. Program DeZip;
  2.  
  3. {   DeZip v2.0 (C) Copyright 1989 by R. P. Byrne                              }
  4. {                                                                             }
  5. {   This is a "bare-bones" program to extract files from ZIP archives.        }
  6. {   By "bare-bones", I mean that there is no facility included to do anything }
  7. {   but extraction (ie. no echo to console, no send to printer, etc.).        }
  8. {   If relative pathnames are stored in the Zip file, make sure all of the    }
  9. {   required directories exist on your system before attempting an            }
  10. {   extraction.                                                               }
  11.  
  12. {$M 10240, 0, 0}           { Stack, Min. Heap, Max. Heap}
  13. {$F+}                      { Force far calls }
  14.  
  15. Uses
  16.    Dos,
  17.    Crt,
  18.    MemAlloc,
  19.    StrProcs;
  20.  
  21. Const
  22.    COPYRIGHT = 'DeZip (C) Copyright 1989 by R. P. Byrne';
  23.    VERSION   = 'Version 2.0 - Compiled on July 31, 1989';
  24.  
  25. { Stuff needed generically by all uncompression methods }
  26.  
  27. Const
  28.    MAXNAMES = 20;
  29.  
  30. Var
  31.    InFileSpecs :  Array[1..MAXNAMES] of String;   { Input file specifications }
  32.    MaxSpecs    :  Word;        { Total number of entries in InFileSpecs array }
  33.    OutPath     :  String;      { Output path specification                    }
  34.  
  35.    TenPercent  :  LongInt;
  36.  
  37. { Define ZIP file header types }
  38.  
  39. Const
  40.    LOCAL_FILE_HEADER_SIGNATURE = $04034B50;
  41.  
  42. Type
  43.    Local_File_Header_Type = Record
  44.                              { Signature              :  LongInt; }
  45.                                Extract_Version_Reqd   :  Word;
  46.                                Bit_Flag               :  Word;
  47.                                Compress_Method        :  Word;
  48.                                Last_Mod_Time          :  Word;
  49.                                Last_Mod_Date          :  Word;
  50.                                Crc32                  :  LongInt;
  51.                                Compressed_Size        :  LongInt;
  52.                                Uncompressed_Size      :  LongInt;
  53.                                Filename_Length        :  Word;
  54.                                Extra_Field_Length     :  Word;
  55.                             end;
  56.  
  57. Const
  58.    CENTRAL_FILE_HEADER_SIGNATURE = $02014B50;
  59.  
  60. Type
  61.    Central_File_Header_Type = Record
  62.                                { Signature            :  LongInt; }
  63.                                  MadeBy_Version       :  Word;
  64.                                  Extract_Version_Reqd :  Word;
  65.                                  Bit_Flag             :  Word;
  66.                                  Compress_Method      :  Word;
  67.                                  Last_Mod_Time        :  Word;
  68.                                  Last_Mod_Date        :  Word;
  69.                                  Crc32                :  LongInt;
  70.                                  Compressed_Size      :  LongInt;
  71.                                  Uncompressed_Size    :  LongInt;
  72.                                  Filename_Length      :  Word;
  73.                                  Extra_Field_Length   :  Word;
  74.                                  File_Comment_Length  :  Word;
  75.                                  Starting_Disk_Num    :  Word;
  76.                                  Internal_Attributes  :  Word;
  77.                                  External_Attributes  :  LongInt;
  78.                                  Local_Header_Offset  :  LongInt;
  79.                               End;
  80.  
  81. Const
  82.    END_OF_CENTRAL_DIR_SIGNATURE = $06054B50;
  83.  
  84. Type
  85.    End_of_Central_Dir_Type =  Record
  86.                                { Signature               :  LongInt; }
  87.                                  Disk_Number             :  Word;
  88.                                  Central_Dir_Start_Disk  :  Word;
  89.                                  Entries_This_Disk       :  Word;
  90.                                  Total_Entries           :  Word;
  91.                                  Central_Dir_Size        :  LongInt;
  92.                                  Start_Disk_Offset       :  LongInt;
  93.                                  ZipFile_Comment_Length  :  Word;
  94.                               end;
  95.  
  96. Const
  97.    CRC_32_TAB : Array[0..255] of LongInt = (
  98. $00000000, $77073096, $ee0e612c, $990951ba, $076dc419, $706af48f, $e963a535, $9e6495a3,
  99. $0edb8832, $79dcb8a4, $e0d5e91e, $97d2d988, $09b64c2b, $7eb17cbd, $e7b82d07, $90bf1d91,
  100. $1db71064, $6ab020f2, $f3b97148, $84be41de, $1adad47d, $6ddde4eb, $f4d4b551, $83d385c7,
  101. $136c9856, $646ba8c0, $fd62f97a, $8a65c9ec, $14015c4f, $63066cd9, $fa0f3d63, $8d080df5,
  102. $3b6e20c8, $4c69105e, $d56041e4, $a2677172, $3c03e4d1, $4b04d447, $d20d85fd, $a50ab56b,
  103. $35b5a8fa, $42b2986c, $dbbbc9d6, $acbcf940, $32d86ce3, $45df5c75, $dcd60dcf, $abd13d59,
  104. $26d930ac, $51de003a, $c8d75180, $bfd06116, $21b4f4b5, $56b3c423, $cfba9599, $b8bda50f,
  105. $2802b89e, $5f058808, $c60cd9b2, $b10be924, $2f6f7c87, $58684c11, $c1611dab, $b6662d3d,
  106. $76dc4190, $01db7106, $98d220bc, $efd5102a, $71b18589, $06b6b51f, $9fbfe4a5, $e8b8d433,
  107. $7807c9a2, $0f00f934, $9609a88e, $e10e9818, $7f6a0dbb, $086d3d2d, $91646c97, $e6635c01,
  108. $6b6b51f4, $1c6c6162, $856530d8, $f262004e, $6c0695ed, $1b01a57b, $8208f4c1, $f50fc457,
  109. $65b0d9c6, $12b7e950, $8bbeb8ea, $fcb9887c, $62dd1ddf, $15da2d49, $8cd37cf3, $fbd44c65,
  110. $4db26158, $3ab551ce, $a3bc0074, $d4bb30e2, $4adfa541, $3dd895d7, $a4d1c46d, $d3d6f4fb,
  111. $4369e96a, $346ed9fc, $ad678846, $da60b8d0, $44042d73, $33031de5, $aa0a4c5f, $dd0d7cc9,
  112. $5005713c, $270241aa, $be0b1010, $c90c2086, $5768b525, $206f85b3, $b966d409, $ce61e49f,
  113. $5edef90e, $29d9c998, $b0d09822, $c7d7a8b4, $59b33d17, $2eb40d81, $b7bd5c3b, $c0ba6cad,
  114. $edb88320, $9abfb3b6, $03b6e20c, $74b1d29a, $ead54739, $9dd277af, $04db2615, $73dc1683,
  115. $e3630b12, $94643b84, $0d6d6a3e, $7a6a5aa8, $e40ecf0b, $9309ff9d, $0a00ae27, $7d079eb1,
  116. $f00f9344, $8708a3d2, $1e01f268, $6906c2fe, $f762575d, $806567cb, $196c3671, $6e6b06e7,
  117. $fed41b76, $89d32be0, $10da7a5a, $67dd4acc, $f9b9df6f, $8ebeeff9, $17b7be43, $60b08ed5,
  118. $d6d6a3e8, $a1d1937e, $38d8c2c4, $4fdff252, $d1bb67f1, $a6bc5767, $3fb506dd, $48b2364b,
  119. $d80d2bda, $af0a1b4c, $36034af6, $41047a60, $df60efc3, $a867df55, $316e8eef, $4669be79,
  120. $cb61b38c, $bc66831a, $256fd2a0, $5268e236, $cc0c7795, $bb0b4703, $220216b9, $5505262f,
  121. $c5ba3bbe, $b2bd0b28, $2bb45a92, $5cb36a04, $c2d7ffa7, $b5d0cf31, $2cd99e8b, $5bdeae1d,
  122. $9b64c2b0, $ec63f226, $756aa39c, $026d930a, $9c0906a9, $eb0e363f, $72076785, $05005713,
  123. $95bf4a82, $e2b87a14, $7bb12bae, $0cb61b38, $92d28e9b, $e5d5be0d, $7cdcefb7, $0bdbdf21,
  124. $86d3d2d4, $f1d4e242, $68ddb3f8, $1fda836e, $81be16cd, $f6b9265b, $6fb077e1, $18b74777,
  125. $88085ae6, $ff0f6a70, $66063bca, $11010b5c, $8f659eff, $f862ae69, $616bffd3, $166ccf45,
  126. $a00ae278, $d70dd2ee, $4e048354, $3903b3c2, $a7672661, $d06016f7, $4969474d, $3e6e77db,
  127. $aed16a4a, $d9d65adc, $40df0b66, $37d83bf0, $a9bcae53, $debb9ec5, $47b2cf7f, $30b5ffe9,
  128. $bdbdf21c, $cabac28a, $53b39330, $24b4a3a6, $bad03605, $cdd70693, $54de5729, $23d967bf,
  129. $b3667a2e, $c4614ab8, $5d681b02, $2a6f2b94, $b40bbe37, $c30c8ea1, $5a05df1b, $2d02ef8d
  130. );
  131.  
  132. Const
  133.    BUFSIZE       = 8192;           { Size of buffers for I/O }
  134.  
  135. Type
  136.    BufPtr        = ^BufType;
  137.    BufType      = Array[1..BUFSIZE] of Byte;
  138.  
  139. Var
  140.    ZipName       :  String;         { Name of Zip file to be processed }
  141.    ZipFile       :  File;           { Zip file variable }
  142.    EndFile       :  Boolean;        { End of file indicator for ZipFile }
  143.    ZipBuf        :  BufPtr;         { Input buffer for ZipFile }
  144.    ZipPtr        :  Word;           { Index for ZipFile input buffer }
  145.    ZipCount      :  Word;           { Count of bytes in ZipFile input buffer }
  146.  
  147.    ExtFile       :  File;           { Output file variable }
  148.    ExtBuf        :  BufPtr;         { Output buffer for ExtFile }
  149.    ExtPtr        :  Word;           { Index for ExtFile output buffer }
  150.    ExtCount      :  LongInt;        { Count of characters written to output }
  151.  
  152.    LocalHdr       : Local_File_Header_Type;  { Storage for a local file hdr }
  153.    Hdr_FileName   : String;
  154.    Hdr_ExtraField : String;
  155.    Hdr_Comment    : String;
  156.  
  157.    Crc32Val      :  LongInt;        { Running CRC (32 bit) value }
  158.  
  159.    Bytes_To_Go   :  LongInt;        { Bytes left to process in compressed file }
  160.  
  161.  
  162. { Stuff needed for unSHRINKing }
  163.  
  164. Const
  165.    MINCODESIZE    =    9;
  166.    MAXCODESIZE    =   13;
  167.    SPECIAL        =  256;
  168.    FIRSTFREE      =  257;
  169.    LZW_TABLE_SIZE =  (1 SHL MAXCODESIZE) - 1;      { 0..8191 }
  170.    LZW_STACK_SIZE =  (1 SHL MAXCODESIZE) - 1;      { 0..8191 }
  171.  
  172. Type
  173.  
  174.    LZW_Table_Rec  =  Record
  175.                         Prefix      :  Integer;
  176.                         Suffix      :  Byte;
  177.                         ChildCount  :  Word;  { If ChildCount = 0 then leaf node }
  178.                      end;
  179.    LZW_Table_Ptr  =  ^LZW_Table_Type;
  180.    LZW_Table_Type =  Array[0..LZW_TABLE_SIZE] of LZW_Table_Rec;
  181.  
  182.    FreeListPtr    =  ^FreeListArray;
  183.    FreeListArray  =  Array[FIRSTFREE..LZW_TABLE_SIZE] of Word;
  184.  
  185.    StackPtr       =  ^StackType;
  186.    StackType      =  Array[0..LZW_STACK_SIZE] of Word;
  187.  
  188. Var
  189.    LZW_Table   :  LZW_Table_Ptr; { Code table for LZW decoding                }
  190.    FreeList    :  FreeListPtr;   { List of free table entries                 }
  191.    NextFree    :  Word;          { Index for free list array                  }
  192.                                  {   FreeList^[NextFree] always contains the  }
  193.                                  {   index of the next available entry in     }
  194.                                  {   the LZW Prefix:Suffix table (LZW_Table^) }
  195.    LZW_Stack   :  StackPtr;      { A stack used to build decoded strings      }
  196.    StackIdx    :  Word;          { Stack array index variable                 }
  197.                                  {   StackIdx always points to the next       }
  198.                                  {   available entry in the stack             }
  199.    SaveByte    :  Byte;          { Our input code buffer - 1 byte long }
  200.    BitsLeft    :  Byte;          { Unprocessed bits in the input code buffer }
  201.    FirstCh     :  Boolean;       { Flag indicating first char being processed }
  202.  
  203.  
  204. { Stuff needed for unREDUCEing }
  205.  
  206. Const
  207.    MAXDICTSIZE    =  8192;       { size will be 4096 for unreduce and either  }
  208.                                  { 4096 or 8192 for exploding                 }
  209.  
  210. Type
  211.    FollowerSet    =  Record
  212.                         SetSize  :  Word;
  213.                         FSet     :  Array[0..31] of Byte;
  214.                      end;
  215.    FollowerPtr    =  ^FollowerArray;
  216.    FollowerArray  =  Array[0..255] of FollowerSet;
  217.  
  218.    DictPtr        =  ^DictArray;
  219.    DictArray      =  Array[0..MAXDICTSIZE - 1] of Byte;
  220.  
  221. Var
  222.    Followers   :  FollowerPtr;
  223.    Dictionary  :  DictPtr;       { The sliding dictionary }
  224.    DictIdx     :  Word;          { Always points to next pos. to be filled }
  225.    DictSize    :  Word;          { size (in bytes) of sliding dictionary }
  226.    State       :  Byte;
  227.    Len         :  Word;
  228.    V           :  Byte;
  229.  
  230.  
  231. { Stuff needed for unIMPLODEing }
  232.  
  233. Const
  234.    MAX_SF_TREE_SIZE     =  511;
  235.  
  236.    LITERAL_TREE_ROOT    =  511;
  237.    DISTANCE_TREE_ROOT   =  127;
  238.    LENGTH_TREE_ROOT     =  127;
  239.  
  240. Type
  241.  
  242.    { The following structures are used to define the Shannon-Fano trees used  }
  243.    { in decoding an imploded file                                             }
  244.  
  245.    SF_Node              =  Record
  246.                               LChild   :  Integer;
  247.                               RChild   :  Integer;
  248.                            end;
  249.    SF_Literal_Ptr       =  ^SF_Literal_Array;
  250.    SF_Distance_Ptr      =  ^SF_Distance_Array;
  251.    SF_Length_Ptr        =  ^SF_Length_Array;
  252.    SF_Literal_Array     =  Array[0..LITERAL_TREE_ROOT] of SF_Node;
  253.    SF_Distance_Array    =  Array[0..DISTANCE_TREE_ROOT] of SF_Node;
  254.    SF_Length_Array      =  Array[0..LENGTH_TREE_ROOT] of SF_Node;
  255.  
  256.    { The Shannon-Fano data that is stored at the beginning of the compressed  }
  257.    { file is itself compressed.  The following structures are used to decode  }
  258.    { that data and build the required Shannon-Fano trees                      }
  259.    SF_BuildRec          =  Record
  260.                               Len   :  Byte;
  261.                               Val   :  Byte;
  262.                               Code  :  Word;
  263.                            end;
  264.    SF_BuildPtr          =  ^SF_BuildArray;
  265.    SF_BuildArray        =  Array[0..255] of SF_BuildRec;
  266.  
  267. Var
  268.    SF_Literal           :  SF_Literal_Ptr;   { These are the 3 Shannon-Fano   }
  269.    SF_Distance          :  SF_Distance_Ptr;  { trees that are used to implode }
  270.    SF_Length            :  SF_Length_Ptr;    { a file.                        }
  271.                                              
  272.    NextFreeLiteral      :  Word;    { Free node pointers used while trees     }
  273.    NextFreeLength       :  Word;    { are being constructed                   }
  274.    NextFreeDistance     :  Word;
  275.  
  276.    SF_Build             :  SF_BuildPtr;      { Array used in building the     }
  277.                                              { Shannon-Fano trees needed to   }
  278.                                              { decode the imploded file       }
  279.  
  280.    SF_Build_Idx         :  Byte;    { Index var for SF_Build array            }
  281.  
  282.    NumOfTrees           :  Byte;    { the # of SF trees needed (2 or 3)       }
  283.    MinMatchLen          :  Byte;    { minimum dictionary match length (2 or 3)}
  284.       
  285.  
  286. { --------------------------------------------------------------------------- }
  287.  
  288. Procedure Abort(Msg : String);
  289. Begin
  290.    Writeln;
  291.    Writeln(Msg);
  292.    Writeln('Returning to DOS');
  293.    Writeln;
  294.    Halt;
  295. end {Abort};
  296.  
  297. { --------------------------------------------------------------------------- }
  298.  
  299. Procedure Syntax;
  300. Begin
  301.    Writeln('Usage:  DeZip ZipFileName [OutPathSpec] [FileSpec [...]]');
  302.    Writeln;
  303.    Writeln('Optional file specifications may contain DOS ');
  304.    Writeln('wildcard characters.');
  305.    Writeln;
  306.    Writeln('If no filespecs are entered, *.* is assumed.');
  307.    Writeln;
  308.    Halt;
  309. End;
  310.  
  311. { --------------------------------------------------------------------------- }
  312.  
  313. Function HexLInt(L : LongInt) : String;
  314. Type
  315.    HexType  = Array [0..15] of Char;
  316. Const
  317.    HexChar : HexType = ('0','1','2','3','4','5','6','7',
  318.                         '8','9','A','B','C','D','E','F');
  319. Begin
  320.    HexLInt  := HexChar[(L AND $F0000000) SHR 28] +
  321.                HexChar[(L AND $0F000000) SHR 24] +
  322.                HexChar[(L AND $00F00000) SHR 20] +
  323.                HexChar[(L AND $000F0000) SHR 16] +
  324.                HexChar[(L AND $0000F000) SHR 12] +
  325.                HexChar[(L AND $00000F00) SHR  8] +
  326.                HexChar[(L AND $000000F0) SHR  4] +
  327.                HexChar[(L AND $0000000F)       ] +
  328.                'h';
  329. end {HexLInt};
  330.  
  331. { --------------------------------------------------------------------------- }
  332.  
  333. Function IO_Test : Boolean;
  334. Var
  335.    ErrorCode   :  Word;
  336.    CodeStr     :  String;
  337.    Ok          :  Boolean;
  338. Begin
  339.    Ok := TRUE;
  340.    ErrorCode := IOResult;
  341.    If ErrorCode <> 0 then begin
  342.       Ok := FALSE;
  343.       Case ErrorCode of
  344.            2 : Writeln('File Not Found');
  345.            3 : Writeln('Path Not Found');
  346.            5 : Writeln('Access Denied');
  347.          101 : Writeln('Disk Full');
  348.         else   Writeln('I/O Error # ', ErrorCode);
  349.       end {Case};
  350.    end {if};
  351.    IO_Test := Ok;
  352. end {IO_Test};
  353.  
  354. { --------------------------------------------------------------------------- }
  355.  
  356. Procedure Load_Parms;
  357. Var
  358.    I      : Word;
  359.    Name   : String;
  360.    DosDTA : SearchRec;
  361. Begin
  362.    I := ParamCount;
  363.    If I < 1 then
  364.       Syntax;
  365.  
  366.    ZipName := ParamStr(1);
  367.    For I := 1 to Length(ZipName) do
  368.       ZipName[I] := UpCase(ZipName[I]);
  369.    If Pos('.', ZipName) = 0 then
  370.       ZipName := ZipName  + '.ZIP';
  371.  
  372.    MaxSpecs := 0;
  373.    OutPath := '';
  374.    I := 1;
  375.    While I < ParamCount do begin
  376.       Inc(I);
  377.       Name := ParamStr(I);
  378.       If Name[length(Name)] = '\' then
  379.          Delete(Name, length(Name), 1);
  380.       FindFirst(Name, DIRECTORY, DosDTA);     { outpath spec? }
  381.       If DosError = 0 then begin
  382.          If (DosDTA.Attr AND DIRECTORY) <> 0 then begin   { yup }
  383.             OutPath := Name;
  384.             If OutPath[Length(OutPath)] <> '\' then
  385.                OutPath := OutPath + '\';
  386.          end {then}
  387.          else begin
  388.             If MaxSpecs < MAXNAMES then begin
  389.                Inc(MaxSpecs);
  390.                InFileSpecs[MaxSpecs] := Name;
  391.             end {if};
  392.          end {if};
  393.       end {then}
  394.       else begin
  395.          If MaxSpecs < MAXNAMES then begin
  396.             Inc(MaxSpecs);
  397.             InFileSpecs[MaxSpecs] := Name;
  398.          end {if};
  399.       end {if}
  400.    end {while};
  401.  
  402.    If MaxSpecs = 0 then begin
  403.       MaxSpecs := 1;
  404.       InFileSpecs[1] := '*.*';
  405.    end {if};
  406.  
  407. end {Load_Parms};
  408.  
  409. { --------------------------------------------------------------------------- }
  410.  
  411. Procedure Initialize;
  412. Var
  413.    Code : Integer;
  414. Begin
  415.    Code := Malloc(ZipBuf, SizeOf(ZipBuf^)) OR
  416.            Malloc(ExtBuf, SizeOf(ExtBuf^));
  417.    If Code <> 0 then
  418.       Abort('Not enough memory available to allocate I/O buffers!');
  419. end {Initialize};
  420.  
  421. { --------------------------------------------------------------------------- }
  422.  
  423. { Converted to Turbo Pascal (tm) V4.0 March, 1988 by J.R.Louvau               }
  424. { COPYRIGHT (C) 1986 Gary S. Brown.  You may use this program, or             }
  425. { code or tables extracted from it, as desired without restriction.           }
  426. {                                                                             }
  427. { First, the polynomial itself and its table of feedback terms.  The          }
  428. { polynomial is                                                               }
  429. { X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0         }
  430. {                                                                             }
  431. { Note that we take it "backwards" and put the highest-order term in          }
  432. { the lowest-order bit.  The X^32 term is "implied"; the LSB is the           }
  433. { X^31 term, etc.  The X^0 term (usually shown as "+1") results in            }
  434. { the MSB being 1.                                                            }
  435. {                                                                             }
  436. { Note that the usual hardware shift register implementation, which           }
  437. { is what we're using (we're merely optimizing it by doing eight-bit          }
  438. { chunks at a time) shifts bits into the lowest-order term.  In our           }
  439. { implementation, that means shifting towards the right.  Why do we           }
  440. { do it this way?  Because the calculated CRC must be transmitted in          }
  441. { order from highest-order term to lowest-order term.  UARTs transmit         }
  442. { characters in order from LSB to MSB.  By storing the CRC this way,          }
  443. { we hand it to the UART in the order low-byte to high-byte; the UART         }
  444. { sends each low-bit to hight-bit; and the result is transmission bit         }
  445. { by bit from highest- to lowest-order term without requiring any bit         }
  446. { shuffling on our part.  Reception works similarly.                          }
  447. {                                                                             }
  448. { The feedback terms table consists of 256, 32-bit entries.  Notes:           }
  449. {                                                                             }
  450. {     The table can be generated at runtime if desired; code to do so         }
  451. {     is shown later.  It might not be obvious, but the feedback              }
  452. {     terms simply represent the results of eight shift/xor opera-            }
  453. {     tions for all combinations of data and CRC register values.             }
  454. {                                                                             }
  455. {     The values must be right-shifted by eight bits by the "updcrc"          }
  456. {     logic; the shift must be unsigned (bring in zeroes).  On some           }
  457. {     hardware you could probably optimize the shift in assembler by          }
  458. {     using byte-swap instructions.                                           }
  459. {     polynomial $edb88320                                                    }
  460. {                                                                             }
  461.  
  462. Function UpdC32(Octet: Byte; Crc: LongInt) : LongInt;
  463. Var
  464.    L : LongInt;
  465.    W : Array[1..4] of Byte Absolute L;
  466. Begin
  467.  
  468.    UpdC32 := CRC_32_TAB[Byte(Crc XOR LongInt(Octet))] XOR ((Crc SHR 8) AND $00FFFFFF);
  469.  
  470. end {UpdC32};
  471.  
  472. { --------------------------------------------------------------------------- }
  473.  
  474. Procedure Read_Zip_Block;
  475. Begin
  476.    BlockRead(ZipFile, ZipBuf^, BUFSIZE, ZipCount);
  477.    If ZipCount = 0 then
  478.       EndFile := TRUE;
  479.    ZipPtr := 1;
  480. End {Read_Zip_Block};
  481.  
  482. { --------------------------------------------------------------------------- }
  483.  
  484. Procedure Write_Ext_Block;
  485. Begin
  486.    If ExtPtr > 1 then begin
  487.       BlockWrite(ExtFile, ExtBuf^, Pred(ExtPtr));
  488.       If NOT IO_Test then
  489.          Halt;
  490.       ExtPtr := 1;
  491.    end {if};
  492. End {Write_Ext_Block};
  493.  
  494. { --------------------------------------------------------------------------- }
  495.  
  496. Procedure Open_Zip;
  497. Begin
  498.    Assign(ZipFile, ZipName);
  499.    FileMode := 64;  { Read Only / Deny None }
  500.    {$I-} Reset(ZipFile, 1) {$I+};
  501.    If NOT IO_Test then
  502.       Halt;
  503.    EndFile := FALSE;
  504.    Read_Zip_Block;
  505. End {Open_Zip};
  506.  
  507. { --------------------------------------------------------------------------- }
  508.  
  509. Function Open_Ext : Boolean;
  510. Begin
  511.    Assign(ExtFile, OutPath + Hdr_FileName);
  512.    FileMode := 66;  { Read & Write / Deny None }
  513.    {$I-} Rewrite(ExtFile, 1) {$I+};
  514.    If NOT IO_Test then
  515.       Open_Ext := FALSE
  516.    else begin
  517.       ExtPtr := 1;
  518.       Open_Ext := TRUE;
  519.    end {if};
  520. end {Open_Ext};
  521.  
  522. { --------------------------------------------------------------------------- }
  523.  
  524. Function Get_Zip : Integer;
  525. Begin
  526.    If ZipPtr > ZipCount then
  527.       Read_Zip_Block;
  528.  
  529.    If EndFile then
  530.       Get_Zip := -1
  531.    else begin
  532.       Get_Zip := ZipBuf^[ZipPtr];
  533.       Inc(ZipPtr);
  534.    end {if};
  535. end {Get_Zip};
  536.  
  537. { --------------------------------------------------------------------------- }
  538.  
  539. Procedure Put_Ext(C : Byte);
  540. Begin
  541.    Crc32Val := UpdC32(C, Crc32Val);
  542.    ExtBuf^[ExtPtr] := C;
  543.    Inc(ExtPtr);
  544.    Inc(ExtCount);
  545.    If ExtPtr > BUFSIZE then
  546.       Write_Ext_Block;
  547. end {Put_Ext};
  548.  
  549. { --------------------------------------------------------------------------- }
  550.  
  551. Procedure Close_Zip;
  552. Begin
  553.    {$I-} Close(Zipfile) {$I+};
  554.    If IO_Test then ;
  555. end {Close_Zip};
  556.  
  557. { --------------------------------------------------------------------------- }
  558.  
  559. Procedure Close_Ext;
  560. Type
  561.    TimeDateRec = Record
  562.                     Time : Word;
  563.                     Date : Word;
  564.                  end {record};
  565. Var
  566.    TimeDate      : TimeDateRec;
  567.    TimeDateStamp : LongInt Absolute TimeDate;
  568. Begin
  569.    Write_Ext_Block;
  570.    TimeDate.Time := LocalHdr.Last_Mod_Time;
  571.    TimeDate.Date := LocalHdr.Last_Mod_Date;
  572.    SetFTime(ExtFile, TimeDateStamp);
  573.    {$I-} Close(ExtFile) {$I+};
  574.    If IO_Test then ;
  575. end {Close_Ext};
  576.  
  577. { --------------------------------------------------------------------------- }
  578.  
  579. Procedure FSkip(Offset : LongInt);
  580. Var
  581.    Rec : LongInt;
  582. Begin
  583.    If (Offset + ZipPtr) <= ZipCount then
  584.       Inc(ZipPtr, Offset)
  585.    else begin
  586.       Rec := FilePos(ZipFile) + (Offset - (ZipCount - ZipPtr) - 1);
  587.       {$I-} Seek(ZipFile, Rec) {$I+};
  588.       If NOT IO_Test then
  589.          Halt;
  590.       Read_Zip_Block;
  591.    end {if};
  592. end {FSkip};
  593.  
  594. { --------------------------------------------------------------------------- }
  595.  
  596. Procedure FRead(Var Buf; RecLen : Word);
  597. Var
  598.    I  :  Word;
  599.    B  :  Array[1..MaxInt] of Byte Absolute Buf;
  600. Begin
  601.    For I := 1 to RecLen do
  602.       B[I] := Get_Zip;
  603. end {FRead};
  604.  
  605. { --------------------------------------------------------------------------- }
  606.  
  607. Function Read_Local_Hdr : Boolean;
  608. Var
  609.    Sig : LongInt;
  610. Begin
  611.    If EndFile then
  612.       Read_Local_Hdr := FALSE
  613.    else begin
  614.       FRead(Sig, SizeOf(Sig));
  615.       If Sig = CENTRAL_FILE_HEADER_SIGNATURE then begin
  616.          Read_Local_Hdr := FALSE;
  617.          EndFile        := TRUE;
  618.       end {then}
  619.       else begin
  620.          If Sig <> LOCAL_FILE_HEADER_SIGNATURE then
  621.             Abort('Missing or invalid local file header in ' + ZipName);
  622.          FRead(LocalHdr, SizeOf(LocalHdr));
  623.          With LocalHdr do begin
  624.             If FileName_Length > 255 then
  625.                Abort('Filename of compressed file exceeds 255 characters!');
  626.             FRead(Hdr_FileName[1], FileName_Length);
  627.             Hdr_FileName[0] := Chr(FileName_Length);
  628.             If Extra_Field_Length > 255 then
  629.                Abort('Extra field of compressed file exceeds 255 characters!');
  630.             FRead(Hdr_ExtraField[1], Extra_Field_Length);
  631.             Hdr_ExtraField[0] := Chr(Extra_Field_Length);
  632.          end {with};
  633.          Read_Local_Hdr := TRUE;
  634.       end {if};
  635.    end {if};
  636. end {Read_Local_Hdr};
  637.  
  638. { --------------------------------------------------------------------------- }
  639.  
  640. Function Get_Compressed : Integer;
  641. Var
  642.    PctDone : Integer;
  643. Begin
  644.    If Bytes_To_Go = 0 then
  645.       Get_Compressed := -1
  646.    else begin
  647.       Get_Compressed := Get_Zip;
  648.       If Bytes_To_Go mod TenPercent = 0 then begin
  649.          PctDone := 100 - Round( 100 * (Bytes_To_Go / LocalHdr.Compressed_Size));
  650.          GotoXY(WhereX - 4, WhereY);
  651.          Write(PctDone:3, '%');
  652.       end {if};
  653.       Dec(Bytes_To_Go);
  654.    end {if};
  655. end {Get_Compressed};
  656.  
  657. { --------------------------------------------------------------------------- }
  658.  
  659. Function LZW_Init : Boolean;
  660. Var
  661.    RC       :  Word;
  662.    I        :  Word;
  663. Label
  664.    Exit;
  665. Begin
  666.    { Initialize LZW Table }
  667.    RC := Malloc(LZW_Table, SizeOf(LZW_Table^));
  668.    If RC <> 0 then begin
  669.       LZW_Init := FALSE;
  670.       Goto Exit;
  671.    end {if};
  672.    For I := 0 to LZW_TABLE_SIZE do begin
  673.       With LZW_Table^[I] do begin
  674.          Prefix     := -1;
  675.          If I < 256 then
  676.             Suffix  := I
  677.          else
  678.             Suffix  := 0;
  679.          ChildCount := 0;
  680.       end {with};
  681.    end {for};
  682.  
  683.    RC := Malloc(FreeList, SizeOf(FreeList^));
  684.    If RC <> 0 then begin
  685.       LZW_Init := FALSE;
  686.       Goto Exit;
  687.    end {if};
  688.    For I := FIRSTFREE to LZW_TABLE_SIZE do
  689.       FreeList^[I] := I;
  690.    NextFree := FIRSTFREE;
  691.  
  692.    { Initialize the LZW Character Stack }
  693.    RC := Malloc(LZW_Stack, SizeOf(LZW_Stack^));
  694.    If RC <> 0 then begin
  695.       LZW_Init := FALSE;
  696.       Goto Exit;
  697.    end {if};
  698.    StackIdx := 0;
  699.    LZW_Init := TRUE;
  700.  
  701. Exit:
  702. end {LZW_Init};
  703.  
  704. { --------------------------------------------------------------------------- }
  705.  
  706. Procedure LZW_Cleanup;
  707. Var
  708.    Code : Word;
  709. Begin
  710.    Code := Dalloc(LZW_Table);
  711.    Code := Dalloc(FreeList);
  712.    Code := Dalloc(LZW_Stack);
  713. end {LZW_Cleanup};
  714.  
  715. { --------------------------------------------------------------------------- }
  716.  
  717. Procedure Clear_LZW_Table;
  718. Var
  719.    I      :  Word;
  720. Begin
  721.    StackIdx := 0;
  722.  
  723.    For I := FIRSTFREE to LZW_TABLE_SIZE do begin      { Find all leaf nodes }
  724.       If LZW_Table^[I].ChildCount = 0 then begin
  725.          LZW_Stack^[StackIdx] := I;                   { and put each on stack }
  726.          Inc(StackIdx);
  727.       end {if};
  728.    end {for};
  729.  
  730.    NextFree := Succ(LZW_TABLE_SIZE);
  731.  
  732.    While StackIdx > 0 do begin                        { clear all leaf nodes }
  733.       Dec(StackIdx);
  734.       I := LZW_Stack^[StackIdx];
  735.       With LZW_Table^[I] do begin
  736.          If LZW_Table^[I].Prefix <> -1 then
  737.             Dec(LZW_Table^[Prefix].ChildCount);
  738.          Prefix     := -1;
  739.          Suffix     :=  0;
  740.          ChildCount :=  0;
  741.       end {with};
  742.       Dec(NextFree);                         { add cleared nodes to freelist }
  743.       FreeList^[NextFree] := I;
  744.    end {while};
  745.  
  746. End {Clear_LZW_Table};
  747.  
  748. { --------------------------------------------------------------------------- }
  749.  
  750. Procedure Add_To_LZW_Table(Prefix : Integer; Suffix : Byte);
  751. Var
  752.    I  :  Word;
  753. Begin
  754.  
  755.    If NextFree <= LZW_TABLE_SIZE then begin
  756.       I := FreeList^[NextFree];
  757.       Inc(NextFree);
  758.       LZW_Table^[I].Prefix     := Prefix;
  759.       LZW_Table^[I].Suffix     := Suffix;
  760.       Inc(LZW_Table^[Prefix].ChildCount);
  761.    end {if};
  762.  
  763. End {Add_To_LZW_Table};
  764.  
  765. { --------------------------------------------------------------------------- }
  766.  
  767. Function GetCode(CodeSize : Byte) : Integer;
  768. Const
  769.    Mask       :  Array[1..8] of Byte = ($01, $03, $07, $0F, $1F, $3F, $7F, $FF);
  770.    TmpInt     : Integer = 0;
  771. Var
  772.    BitsNeeded : Byte;
  773.    HowMany    : Byte;
  774.    HoldCode   : Integer;
  775. Label
  776.    Exit;
  777. Begin
  778.    If FirstCh then begin               { If first time through ...         }
  779.       TmpInt := Get_Compressed;        { ... then prime the code buffer    }
  780.       If TmpInt = -1 then begin        { If EOF on fill attempt ...        }
  781.          GetCode := -1;           { ... then return EOF indicator ... }
  782.          Goto Exit;                    { ... and return to caller.         }
  783.       end {if};
  784.       SaveByte := TmpInt;
  785.       BitsLeft := 8;                   { there's now 8 bits in our buffer  }
  786.       FirstCh  := FALSE;
  787.    end {if};
  788.  
  789.    BitsNeeded := CodeSize;
  790.    HoldCode   := 0;
  791.  
  792.    While (BitsNeeded > 0) And (TmpInt <> -1) do begin
  793.  
  794.       If BitsNeeded >= BitsLeft
  795.          then HowMany := BitsLeft         { HowMany <-- Min(BitsLeft, BitsNeeded) }
  796.          else HowMany := BitsNeeded;
  797.  
  798.       HoldCode := HoldCode OR ((SaveByte AND Mask[HowMany]) SHL (CodeSize - BitsNeeded));
  799.       SaveByte := SaveByte SHR HowMany;
  800.       Dec(BitsNeeded, HowMany);
  801.       Dec(BitsLeft, HowMany);
  802.  
  803.       If BitsLeft <= 0 then begin         { If no bits left in buffer ...     }
  804.          TmpInt := Get_Compressed;        { ... then attempt to get 8 more.   }
  805.          If TmpInt = -1 then
  806.             Goto Exit;
  807.          SaveByte := TmpInt;
  808.          BitsLeft := 8;
  809.       end {if};
  810.  
  811.    end {while};
  812.  
  813. Exit:
  814.  
  815.    If (BitsNeeded = 0) then               { If we got what we came for ... }
  816.       GetCode := HoldCode            { ... then return it             }
  817.    else
  818.       GetCode := -1;                 { ... Otherwise, return EOF      }
  819.  
  820. end {GetCode};
  821.  
  822. { --------------------------------------------------------------------------- }
  823.  
  824. Procedure UnShrink;
  825. Var
  826.    Ch       :  Char;
  827.    CodeSize :  Byte;          { Current size (in bits) of codes coming in  }
  828.    CurrCode :  Integer;
  829.    SaveCode :  Integer;
  830.    PrevCode :  Integer;
  831.    BaseChar :  Byte;
  832. Label
  833.    Exit;
  834. Begin
  835.    CodeSize := MINCODESIZE;               { Start with the smallest code size }
  836.  
  837.    PrevCode := GetCode(CodeSize);        { Get first code from file          }
  838.    If PrevCode = -1 then                  { If EOF already, then ...          }
  839.       Goto Exit;                          { ... just exit without further ado }
  840.    BaseChar := PrevCode;
  841.    Put_Ext(BaseChar);                      { Unpack the first character        }
  842.  
  843.    CurrCode := GetCode(CodeSize);        { Get next code to prime the while loop }
  844.  
  845.    While CurrCode <> -1 do begin          { Repeat for all compressed bytes   }
  846.  
  847.       If CurrCode = SPECIAL then begin    { If we've got a "special" code ... }
  848.  
  849.          CurrCode := GetCode(CodeSize);
  850.          Case CurrCode of
  851.             1  :  Begin                   { ... and if followed by a 1 ...    }
  852.                      Inc(CodeSize);       { ... then increase code size       }
  853.                   end {1};
  854.             2  :  Begin                   { ... and if followed by a 2 ...    }
  855.                      Clear_LZW_Table;     { ... clear leaf nodes in the table }
  856.                   end {2};
  857.             else  begin                   { ... if neither 1 or 2, discard    }
  858.                      Writeln;
  859.                      Writeln('Encountered code 256 not followed by 1 or 2!');
  860.                      Writeln;
  861.                      Write('Press a key to continue ...');
  862.                      Ch := ReadKey;
  863.                      DelLine;
  864.                      GotoXY(1, WhereY);
  865.                   end {else};
  866.          end {case};
  867.  
  868.       end {then}
  869.       else begin                          { Not a "special" code              }
  870.  
  871.          SaveCode := CurrCode;            { Save this code someplace safe...  }
  872.  
  873.          If CurrCode > LZW_TABLE_SIZE then
  874.             Abort('Invalid code encountered!');
  875.  
  876.          If (CurrCode >= FIRSTFREE) and (LZW_Table^[CurrCode].Prefix = -1) then begin
  877.             If StackIdx > LZW_STACK_SIZE then begin
  878.                Write_Ext_Block;
  879.                Writeln;
  880.                Writeln('Stack Overflow (', StackIdx, ')!');
  881.                Halt;
  882.             end {if};
  883.             LZW_Stack^[StackIdx] := BaseChar;
  884.             Inc(StackIdx);
  885.             CurrCode := PrevCode;
  886.          end {if};
  887.  
  888.          While CurrCode >= FIRSTFREE do begin
  889.             If StackIdx > LZW_STACK_SIZE then begin
  890.                Write_Ext_Block;
  891.                Writeln;
  892.                Writeln('Stack Overflow (', StackIdx, ')!');
  893.                Halt;
  894.             end {if};
  895.             LZW_Stack^[StackIdx] := LZW_Table^[CurrCode].Suffix;
  896.             Inc(StackIdx);
  897.             CurrCode := LZW_Table^[CurrCode].Prefix;
  898.          end {while};
  899.  
  900.          BaseChar := LZW_Table^[CurrCode].Suffix;         { Get last character ...   }
  901.          Put_Ext(BaseChar);
  902.  
  903.          While (StackIdx > 0) do begin
  904.             Dec(StackIdx);
  905.             Put_Ext(LZW_Stack^[StackIdx]);
  906.          end {while};                     { ... until there are none left     }
  907.  
  908.          Add_to_LZW_Table(PrevCode, BaseChar);  { Add new entry to table      }
  909.  
  910.          PrevCode := SaveCode;
  911.  
  912.       end {if};
  913.  
  914.       CurrCode := GetCode(CodeSize);     { Get next code from input stream   }
  915.  
  916.    end {while};
  917. Exit:
  918. end {UnShrink};
  919.  
  920. { --------------------------------------------------------------------------- }
  921.  
  922. Function Init_UnReduce : Boolean;
  923. Var
  924.    Code : Word;
  925. Label
  926.    Exit;
  927. Begin
  928.    Code := Malloc(Followers, SizeOf(Followers^));
  929.    If Code <> 0 then begin
  930.       Init_UnReduce := FALSE;
  931.       Goto Exit;
  932.    end {if};
  933.  
  934.    DictSize := 4096;
  935.    Code := Malloc(Dictionary, DictSize);
  936.    If Code <> 0 then begin
  937.       Init_UnReduce := FALSE;
  938.       Goto Exit;
  939.    end {if};
  940.  
  941.    Init_UnReduce := TRUE;
  942.  
  943. Exit:
  944. end {Init_UnReduce};
  945.  
  946. { --------------------------------------------------------------------------- }
  947.  
  948. Procedure Cleanup_UnReduce;
  949. Var
  950.    Code : Word;
  951. Begin
  952.    Code := Dalloc(Followers);
  953.    Code := Dalloc(Dictionary);
  954. end {Cleanup_UnReduce};
  955.  
  956. { --------------------------------------------------------------------------- }
  957.  
  958. Function D(X, Y : Byte) : Word;
  959. Var
  960.    tmp : LongInt;
  961. Begin
  962.    X := X SHR (8 - Pred(LocalHdr.Compress_Method));
  963.    Tmp := X * 256;
  964.    D := Tmp + Y + 1;
  965. end {D};
  966.  
  967. { --------------------------------------------------------------------------- }
  968.  
  969. Function F(X : Word) : Byte;
  970. Const
  971.    TestVal : Array[1..4] of Byte = (127, 63, 31, 15);
  972. Begin
  973.    If X = TestVal[Pred(LocalHdr.Compress_Method)] then
  974.       F := 2
  975.    else
  976.       F := 3;
  977. end {F};
  978.  
  979. { --------------------------------------------------------------------------- }
  980.  
  981. Function L(X : Byte) : Byte;
  982. Const
  983.    Mask : Array[1..4] of Byte = ($7F, $3F, $1F, $0F);
  984. Begin
  985.    L := X AND Mask[Pred(LocalHdr.Compress_Method)];
  986. end {L};
  987.  
  988. { --------------------------------------------------------------------------- }
  989.  
  990. Procedure UpdateDictionary(C : Byte);
  991. Begin
  992.    Put_Ext(C);
  993.    Dictionary^[DictIdx] := C;
  994.    DictIdx := Succ(DictIdx) MOD DictSize;
  995. end {UpdateDictionary};
  996.  
  997. { --------------------------------------------------------------------------- }
  998.  
  999. Procedure DictionaryInit;
  1000. Begin
  1001.    State := 0;
  1002.    FillChar(Dictionary^[0], DictSize, $00);
  1003.    DictIdx := 0;
  1004. end {DictionaryInit};
  1005.  
  1006. { --------------------------------------------------------------------------- }
  1007.  
  1008. Procedure UnScrnch(C : Byte);
  1009. Const
  1010.    DLE   =  $90;
  1011. Var
  1012.    S           :  Integer;
  1013.    Count       :  Word;
  1014.    OneByte     :  Byte;
  1015.    Tmp1        :  LongInt;
  1016. Begin
  1017.    Case State of
  1018.       0  :  begin
  1019.                If C = DLE then
  1020.                   State := 1
  1021.                else
  1022.                   UpdateDictionary(C);
  1023.             end {0};
  1024.       1  :  begin
  1025.                If C = 0 then begin
  1026.                   UpdateDictionary(DLE);
  1027.                   State := 0;
  1028.                end {then}
  1029.                else begin
  1030.                   V     := C;
  1031.                   Len   := L(V);
  1032.                   State := F(Len);
  1033.                end {if};
  1034.             end {1};
  1035.       2  :  begin
  1036.                Inc(Len, C);
  1037.                State := 3;
  1038.             end {2};
  1039.       3  :  begin
  1040.                Tmp1 := D(V, C);
  1041.                S    := DictIdx - Tmp1;
  1042.                If S < 0 then
  1043.                   S := S + DictSize;
  1044.                Count := Len + 3;
  1045.                While Count > 0 do begin
  1046.                   OneByte := Dictionary^[S];
  1047.                   UpdateDictionary(OneByte);
  1048.                   S := Succ(S) MOD DictSize;
  1049.                   Dec(Count);
  1050.                end {while};
  1051.                State := 0;
  1052.             end {3};
  1053.    end {case};
  1054.  
  1055. end {UnScrnch};
  1056.  
  1057. { --------------------------------------------------------------------------- }
  1058.  
  1059. Function MinBits(Val : Byte) : Byte;
  1060. Begin
  1061.    Dec(Val);
  1062.    Case Val of
  1063.        0..1  : MinBits := 1;
  1064.        2..3  : MinBits := 2;
  1065.        4..7  : MinBits := 3;
  1066.        8..15 : MinBits := 4;
  1067.       16..31 : MinBits := 5;
  1068.       else     MinBits := 6;
  1069.    end {case};
  1070. end {MinBits};
  1071.  
  1072. { --------------------------------------------------------------------------- }
  1073.  
  1074. Procedure UnReduce;
  1075. Var
  1076.    LastChar    :  Byte;
  1077.    N           :  Byte;
  1078.    I, J        :  Word;
  1079.    Code        :  Integer;
  1080.    Ch          :  Char;
  1081. Begin
  1082.    For I := 255 downto 0 do begin          { Load follower sets }
  1083.       N := GetCode(6);                { Get size of 1st set }
  1084.       Followers^[I].SetSize := N;
  1085.       If N > 0 then
  1086.          For J := 0 to Pred(N) do
  1087.             Followers^[I].FSet[J] := GetCode(8);
  1088.    end {for};
  1089.  
  1090.    DictionaryInit;
  1091.  
  1092.    LastChar := 0;
  1093.    Repeat
  1094.  
  1095.       If Followers^[LastChar].SetSize = 0 then begin
  1096.          Code := GetCode(8);
  1097.          UnScrnch(Code);
  1098.          LastChar := Code;
  1099.       end {then}
  1100.       else begin
  1101.          Code := GetCode(1);
  1102.          If Code <> 0 then begin
  1103.             Code := GetCode(8);
  1104.             UnScrnch(Code);
  1105.             LastChar := Code;
  1106.          end {then}
  1107.          else begin
  1108.             I := MinBits(Followers^[LastChar].SetSize);
  1109.             Code := GetCode(I);
  1110.             UnScrnch(Followers^[LastChar].FSet[Code]);
  1111.             LastChar := Followers^[LastChar].FSet[Code];
  1112.          end {if};
  1113.       end {if};
  1114.    Until (ExtCount = LocalHdr.Uncompressed_Size);
  1115.    Code := Dalloc(Followers);
  1116. end {UnReduce};
  1117.  
  1118. { --------------------------------------------------------------------------- }
  1119.  
  1120. Function Init_Explode : Boolean;
  1121. { Get ready to unimplode                                                      }
  1122. Var
  1123.    Failure : Boolean;
  1124.    RC      : Word;
  1125. Begin
  1126.    Failure := FALSE;
  1127.  
  1128.    { Extract pertinent info from the general purpose bit flag                 }
  1129.    DictSize    := (((LocalHdr.Bit_Flag SHR 1) and $01) * 4096) + 4096;
  1130.    NumOfTrees  := (( LocalHdr.Bit_Flag SHR 2) and $01) + 2;
  1131.    MinMatchLen := NumOfTrees;
  1132.  
  1133.    { Allocate memory for the Length & Distance Shannon-Fano trees             }
  1134.    RC  :=  Malloc(SF_Length,   SizeOf(SF_Length^))      +
  1135.            Malloc(SF_Distance, SizeOf(SF_Distance^));
  1136.    Failure := Failure or (RC <> 0);
  1137.  
  1138.    { Initialize Length & Distance nodes to all -1's and set the Next Free     }
  1139.    { Node pointers for each                                                   }
  1140.    FillChar(SF_Length^,   SizeOf(SF_Length^),   $FF);
  1141.    NextFreeLength   := Pred(LENGTH_TREE_ROOT);
  1142.    FillChar(SF_Distance^, SizeOf(SF_Distance^), $FF);
  1143.    NextFreeDistance := Pred(DISTANCE_TREE_ROOT);
  1144.  
  1145.    { If we need a literal tree, then allocate the memory , initialize the     }
  1146.    { nodes to all -1's, and set the Next Free Node pointer                    }
  1147.    If NumOfTrees = 3 then begin
  1148.       RC := Malloc(SF_Literal,  SizeOf(SF_Literal^));
  1149.       Failure := Failure or (RC <> 0);
  1150.       FillChar(SF_Literal^, SizeOf(SF_Literal^), $FF);
  1151.       NextFreeLiteral := Pred(LITERAL_TREE_ROOT);
  1152.    end {if};
  1153.  
  1154.    { Allocate memory for the sliding dictionary                               }
  1155.    RC := Malloc(Dictionary,  DictSize);
  1156.    Failure := Failure or (RC <> 0);
  1157.  
  1158.    { Allocate memory for the array used in building the SF-Trees              }
  1159.    RC := Malloc(SF_Build,    SizeOf(SF_Build^));
  1160.    Failure := Failure or (RC <> 0);
  1161.  
  1162.    { If any memory allocations failed, deallocate any memory that may have    }
  1163.    { been successfully allocated.                                             }
  1164.    If Failure then
  1165.       RC       := Dalloc(SF_Length)    +
  1166.                   Dalloc(SF_Distance)  +
  1167.                   Dalloc(SF_Literal)   +
  1168.                   Dalloc(Dictionary)   +
  1169.                   Dalloc(SF_Build);
  1170.  
  1171.    { Return either success or failure }
  1172.    Init_Explode := NOT Failure;
  1173. end { Init_Explode };
  1174.  
  1175. { --------------------------------------------------------------------------- }
  1176.  
  1177. Procedure Cleanup_Explode;
  1178. { Clean things up after unimploding a file }
  1179. Var
  1180.    RC :  Word;
  1181. Begin
  1182.    RC := Dalloc(SF_Length)    +
  1183.          Dalloc(SF_Distance)  +
  1184.          Dalloc(SF_Literal)   +
  1185.          Dalloc(Dictionary)   +
  1186.          Dalloc(SF_Build);
  1187. end { Cleanup_Explode };
  1188.  
  1189. { --------------------------------------------------------------------------- }
  1190.  
  1191. Procedure Bad_SF_Tree;
  1192. Begin
  1193.    Writeln;
  1194.    Abort('Ambiguous Shannon-Fano decode tree encountered!');
  1195. end { Bad_SF_Tree };
  1196.  
  1197. { --------------------------------------------------------------------------- }
  1198.  
  1199. Procedure Add_SF_SubTree( Var SF_Tree;
  1200.                           Var SF_NextFree     : Word;
  1201.                               SF_Root         : Word;
  1202.                               SF_Code         : Word;
  1203.                               SF_Code_Length  : Byte;
  1204.                               SF_Value        : Byte
  1205.                          );
  1206. { Add the subtree defined by SF_Code to a Shannon-Fano tree                   }
  1207. Var
  1208.    SF_Array :  Array [0..MAX_SF_TREE_SIZE] of SF_Node absolute SF_Tree;
  1209.    CurrNode :  Word;
  1210.    LastLeaf :  Word;
  1211.    I        :  Byte;
  1212. Begin
  1213.  
  1214.    { The Shannon-Fano tree is implemented as an array of records. Each        }
  1215.    { record contains both left and right pointers (ie. this is a binary       }
  1216.    { tree).  The root of the tree is the last array element. The first N      }
  1217.    { elements (0..N-1) are defined to be the "leaves" of the tree (ie. they   }
  1218.    { represent the characters that the decode algorithm will generate).  N    }
  1219.    { may be 64 (for the length tree), 128 (for the distance tree), or 256     }
  1220.    { (for the Literal tree). The remaining elements of the array are used to  }
  1221.    { represent the non-leaf and non-root nodes of the tree.                   }
  1222.  
  1223.    CurrNode := SF_Root;
  1224.    LastLeaf := Pred(Succ(SF_Root) DIV 2);
  1225.  
  1226.    { All bits in the code except the least significant define non-leaf nodes  }
  1227.    { in the tree.  Process these first.                                       }
  1228.  
  1229.    For I := Pred(SF_Code_Length) downto 1 do begin
  1230.       If CurrNode <= LastLeaf then
  1231.          Bad_SF_Tree;
  1232.       If Boolean((SF_Code SHR I) AND $0001) then begin   { if the bit is a 1  }
  1233.          If SF_Array[CurrNode].RChild = -1 then begin    { no RChild yet      }
  1234.             SF_Array[CurrNode].RChild := SF_NextFree;
  1235.             Dec(SF_NextFree);
  1236.          end {if};
  1237.          CurrNode := SF_Array[CurrNode].RChild;       { on 1 bits, follow the }
  1238.                                                       { right subtree         }
  1239.       end { then }
  1240.       else begin                                         { the bit is a 0     }
  1241.          If SF_Array[CurrNode].LChild = -1 then begin    { no LChild yet      }
  1242.             SF_Array[CurrNode].LChild := SF_NextFree;
  1243.             Dec(SF_NextFree);
  1244.          end {if};
  1245.          CurrNode := SF_Array[CurrNode].LChild;       { on 0 bits, follow the }
  1246.                                                       { left subtree          }
  1247.       end { if };
  1248.    end { for };
  1249.  
  1250.    { All that's left now is to process the least significant bit of the code. }
  1251.    { This will define a leaf node.  The leaf node to be linked is defined by  }
  1252.    { the SF_Value that is passed to the procedure.                            }
  1253.  
  1254.    If Boolean(SF_Code AND $0001) then
  1255.       If SF_Array[CurrNode].RChild <> -1 then
  1256.          Bad_SF_Tree
  1257.       else
  1258.          SF_Array[CurrNode].RChild := SF_Value
  1259.    else
  1260.       If SF_Array[CurrNode].LChild <> -1 then
  1261.          Bad_SF_Tree
  1262.       else
  1263.          SF_Array[CurrNode].LChild := SF_Value;
  1264.  
  1265. end { Add_SF_SubTree };
  1266.  
  1267. { --------------------------------------------------------------------------- }
  1268.  
  1269. Procedure Sort_SF_Build_Array( Count : Word );
  1270.  
  1271.    Procedure Exchange(Var Node1, Node2 : SF_BuildRec);
  1272.    Var
  1273.       Node3 : SF_BuildRec;
  1274.    Begin
  1275.       Node3.Len  := Node1.Len;
  1276.       Node3.Val  := Node1.Val;
  1277.     { Node3.Code := Node1.Code; }   { the Code field is irrelevant at this point }
  1278.  
  1279.       Node1.Len  := Node2.Len;
  1280.       Node1.Val  := Node2.Val;
  1281.     { Node1.Code := Node2.Code; }   { ditto }
  1282.  
  1283.       Node2.Len  := Node3.Len;
  1284.       Node2.Val  := Node3.Val;
  1285.     { Node2.Code := Node3.Code; }   { ditto again }
  1286.    end { Exchange };
  1287.  
  1288.    function ShouldSwap( P1, P2 : SF_BuildRec ) : Boolean;
  1289.    begin
  1290.       If  (P1.Len > P2.Len)  or
  1291.          ((P1.Len = P2.Len)  and
  1292.           (P1.Val > P2.Val))
  1293.       then
  1294.          ShouldSwap := TRUE
  1295.       else
  1296.          ShouldSwap := FALSE;
  1297.    end { ShouldSwap };
  1298.  
  1299.    procedure sort(lb, ub : integer);
  1300.  
  1301.    (***** BUBBLE SORT **************************************************)
  1302.  
  1303.    (*  The list is scanned repeatedly, and adjacent items that are out of
  1304.        order are swapped.  When a pass occurs with no swaps, the list is
  1305.        sorted.  *)
  1306.  
  1307.    var
  1308.       swapped : boolean;
  1309.       cell    : integer;
  1310.    begin
  1311.        repeat
  1312.            swapped := false;
  1313.            for cell := lb to ub - 1 do
  1314.            begin
  1315.                if ShouldSwap(SF_Build^[cell], SF_Build^[cell + 1]) then
  1316.                begin
  1317.                    Exchange(SF_Build^[cell], SF_Build^[cell + 1]);
  1318.                    swapped := true;
  1319.                end;
  1320.            end;
  1321.        until (swapped = false);
  1322.    end;
  1323.  
  1324. Begin
  1325.    Sort(0, Count);
  1326. end { Sort_SF_Build_Array };
  1327.  
  1328. { --------------------------------------------------------------------------- }
  1329.  
  1330. Procedure Build_SF_Trees;
  1331. { Extract SF data from an imploded file and build the required SF trees }
  1332. Var
  1333.    OneByte              :  Byte;    { These "misc" variables are also used in }
  1334.    CodeLen              :  Byte;    { building the SF trees                   }
  1335.    CodeCount            :  Byte;
  1336.  
  1337.    SF_Table_Codes       :  Word;    { # of bytes representing SF tree data - 1}
  1338.    BuildCount           :  Word;    { total entries in SF_Build array         }
  1339.  
  1340.    Code                 :  Word;    { These three variables used in           }
  1341.    CodeIncrement        :  Word;    { constructing the Shannon-Fano codes     }
  1342.    LastBitLength        :  Word;    { that will be used to build the SF trees }
  1343.  
  1344.    WhichTree            :  Word;    { Counter indicating which SF tree is     }
  1345.                                     {   currently under construction          }
  1346.    SF_Tree              :  Pointer;
  1347.    SF_NextFree          :  Word;
  1348.    SF_Root              :  Word;
  1349.  
  1350.    I, J                 :  Word;    { Generic loop counter                    }
  1351.  
  1352. Begin
  1353.  
  1354.    For WhichTree := 1 to NumOfTrees do begin
  1355.       { Before we go any further, determine which subtree-add procedure       }
  1356.       { parameters will be needed on the call to Add_SF_SubTree               }
  1357.       Case NumOfTrees of
  1358.          2  :  Case WhichTree of
  1359.                   1  :  Begin
  1360.                            SF_Tree     := SF_Length;
  1361.                            SF_NextFree := NextFreeLength;
  1362.                            SF_Root     := LENGTH_TREE_ROOT;
  1363.                         end { 1 };
  1364.                   2  :  Begin
  1365.                            SF_Tree     := SF_Distance;
  1366.                            SF_NextFree := NextFreeDistance;
  1367.                            SF_Root     := DISTANCE_TREE_ROOT;
  1368.                         end { 2 };
  1369.                end { case whichtree };
  1370.          3  :  Case WhichTree of
  1371.                   1  :  Begin
  1372.                            SF_Tree     := SF_Literal;
  1373.                            SF_NextFree := NextFreeLiteral;
  1374.                            SF_Root     := LITERAL_TREE_ROOT;
  1375.                         end { 1 };
  1376.                   2  :  Begin
  1377.                            SF_Tree     := SF_Length;
  1378.                            SF_NextFree := NextFreeLength;
  1379.                            SF_Root     := LENGTH_TREE_ROOT;
  1380.                         end { 2 };
  1381.                   3  :  Begin
  1382.                            SF_Tree     := SF_Distance;
  1383.                            SF_NextFree := NextFreeDistance;
  1384.                            SF_Root     := DISTANCE_TREE_ROOT;
  1385.                         end { 3 };
  1386.                end { case whichtree };
  1387.       end { case numoftrees };
  1388.  
  1389.       { Build the Shannon-Fano tree                                           }
  1390.       SF_Build_Idx   := 0;
  1391.       BuildCount     := 0;
  1392.       SF_Table_Codes := GetCode(8);
  1393.       For I := 0 to SF_Table_Codes do begin
  1394.          { Load the SF_Build array with data from the compressed file         }
  1395.          OneByte     := GetCode(8);
  1396.          CodeLen     := (OneByte AND $0F) + 1;
  1397.          CodeCount   := (OneByte SHR 4);
  1398.          For J := 0 to CodeCount do begin
  1399.             SF_Build^[SF_Build_Idx].Len  := CodeLen;
  1400.             SF_Build^[SF_Build_Idx].Val  := SF_Build_Idx;
  1401.             Inc(SF_Build_Idx);
  1402.          end { for J };
  1403.       end { for I };
  1404.       BuildCount := Pred(SF_Build_Idx);
  1405.  
  1406.       { Sort the SF_Build Array based on the Len field                        }
  1407.       Sort_SF_Build_Array(BuildCount);
  1408.  
  1409.       { Generate the SF codes that will be used to grow the SF tree using the }
  1410.       { algorithm outlined in the AppNote.Txt file (as distributed within the }
  1411.       { PKZip v1.0 self extracting ZIP archive).                              }
  1412.       Code           := 0;
  1413.       CodeIncrement  := 0;
  1414.       LastBitLength  := 0;
  1415.       For I := BuildCount downto 0 do begin
  1416.          Inc(Code, CodeIncrement);
  1417.          If SF_Build^[I].Len <> LastBitLength then begin
  1418.             LastBitLength := SF_Build^[I].Len;
  1419.             CodeIncrement := 1 SHL (16 - LastBitLength);
  1420.          end {if};
  1421.          SF_Build^[I].Code := Code SHR (16 - SF_Build^[I].Len);
  1422.  
  1423.          { Ok, we've got a value and a code.  This represents a subtree in    }
  1424.          { the Shannon-Fano tree structure.  Add it to the appropriate tree.  }
  1425.          Add_SF_SubTree(   SF_Tree^,
  1426.                            SF_NextFree,
  1427.                            SF_Root,
  1428.                            SF_Build^[I].Code,
  1429.                            SF_Build^[I].Len,
  1430.                            SF_Build^[I].Val     );
  1431.  
  1432.       end { for buildcount };
  1433.  
  1434.    end { for whichtree };
  1435.  
  1436. end { Build_SF_Trees };
  1437.  
  1438. { --------------------------------------------------------------------------- }
  1439.  
  1440. Procedure Bad_SF_Data;
  1441. Begin
  1442.    Writeln;
  1443.    Abort('Bad Shannon-Fano code encountered in file!')
  1444. end { Bad_SF_Tree };
  1445.  
  1446. { --------------------------------------------------------------------------- }
  1447.  
  1448. Function Decode_SF_Data( Var SF_Tree;
  1449.                              SF_Root : Word
  1450.                         ) : Byte;
  1451. { Read bits from the input file and decode them using one of the 3 possible   }
  1452. { Shannon-Fano trees.  The method is idential to that used in decoding files  }
  1453. { encoded with the Huffman method (popularaly known as "squeezing") in that   }
  1454. { the tree is traced from the root to either the right or left depending on   }
  1455. { the last bit read until finally, one encounteres a leaf node.               }
  1456. Var
  1457.    SF_Array :  Array [0..MAX_SF_TREE_SIZE] of SF_Node absolute SF_Tree;
  1458.    OneBit   :  Byte;
  1459.    CurrNode :  Word;
  1460.    LastLeaf :  Word;
  1461. Begin
  1462.  
  1463.    CurrNode := SF_Root; { We start traversing the tree from it's root node    }
  1464.    LastLeaf := Pred(Succ(SF_Root) DIV 2);
  1465.  
  1466.    While CurrNode > LastLeaf do begin
  1467.       { Walk the tree until you hit a leaf node                               }
  1468.       OneBit := GetCode(1);
  1469.       If Boolean(OneBit and $01) then        { if the bit is a 1 ...          }
  1470.          If SF_Array[CurrNode].RChild = -1 then
  1471.             Bad_SF_Data
  1472.          else
  1473.             CurrNode := SF_Array[CurrNode].RChild
  1474.       else
  1475.          If SF_Array[CurrNode].LChild = -1 then
  1476.             Bad_SF_Data
  1477.          else
  1478.             CurrNode := SF_Array[CurrNode].LChild
  1479.    end { while };
  1480.  
  1481.    Decode_SF_Data := CurrNode;
  1482. end { Decode_SF_Data };
  1483.  
  1484. { --------------------------------------------------------------------------- }
  1485.  
  1486. Procedure Explode;
  1487. Var
  1488.    OneByte     :  Byte;
  1489.    Literal     :  Byte;
  1490.    Length      :  Word;
  1491.    DistVal     :  Word;
  1492.    Distance    :  Word;
  1493.    DictStart   :  Integer;
  1494. Begin
  1495.  
  1496.    Build_SF_Trees;
  1497.    DictionaryInit;
  1498.  
  1499.    Repeat
  1500.       OneByte := GetCode(1);
  1501.       If OneByte <> 0 then begin
  1502.          { This is literal data ... no dictionary lookup involved          }
  1503.          If NumOfTrees = 3 then
  1504.             Literal := Decode_SF_Data(SF_Literal^, LITERAL_TREE_ROOT)
  1505.          else
  1506.             Literal := GetCode(8);
  1507.          UpdateDictionary(Literal);
  1508.       end { then }
  1509.       else begin
  1510.          { Data for output will come from the sliding dictionary           }
  1511.          If DictSize = 8192 then begin
  1512.             Distance := GetCode(7);
  1513.             DistVal  := Decode_SF_Data(SF_Distance^, DISTANCE_TREE_ROOT);
  1514.             Distance := (Distance OR (DistVal SHL 7)) AND $1FFF;
  1515.          end {then}
  1516.          else begin
  1517.             Distance := GetCode(6);
  1518.             DistVal  := Decode_SF_Data(SF_Distance^, DISTANCE_TREE_ROOT);
  1519.             Distance := (Distance OR (DistVal SHL 6)) AND $0FFF;
  1520.          end {if};
  1521.  
  1522.          Length   := Decode_SF_Data( SF_Length^, LENGTH_TREE_ROOT );
  1523.          If Length = 63 then
  1524.             Length := Length + GetCode(8);
  1525.          Length := Length + MinMatchLen;
  1526.  
  1527.          DictStart := DictIdx - (Distance + 1);
  1528.          If DictStart < 0 then
  1529.             DictStart := DictStart + DictSize;
  1530.  
  1531.          While Length > 0 do begin
  1532.             UpdateDictionary(Dictionary^[DictStart]);
  1533.             DictStart := Succ(DictStart) MOD DictSize;
  1534.             Dec(Length);
  1535.          end {while};
  1536.       end {if};
  1537.  
  1538.    Until (ExtCount >= LocalHdr.Uncompressed_Size);
  1539.  
  1540. end { Explode };
  1541.  
  1542. { --------------------------------------------------------------------------- }
  1543.  
  1544. Procedure UnZip;
  1545. Var
  1546.    C  :  Integer;
  1547. Begin
  1548.    Crc32Val    := $FFFFFFFF;
  1549.    Bytes_To_Go := LocalHdr.Compressed_Size;
  1550.    FirstCh     := TRUE;
  1551.  
  1552.    ExtCount    := 0;
  1553.  
  1554.    TenPercent := LocalHdr.Compressed_Size DIV 10;
  1555.    If TenPercent = 0 then TenPercent := 1;
  1556.  
  1557.    Case LocalHdr.Compress_Method of
  1558.       0     :  Begin
  1559.                   While Bytes_to_go > 0 do
  1560.                      Put_Ext(Get_Compressed);
  1561.                end {0 = Stored};
  1562.       1     :  Begin
  1563.                   If LZW_Init then
  1564.                      UnShrink
  1565.                   else begin
  1566.                      Writeln('Not enough memory available to unshrink!');
  1567.                      Writeln('Skipping ', Hdr_FileName, ' ...');
  1568.                      FSkip(LocalHdr.Compressed_Size);
  1569.                      Crc32Val := NOT LocalHdr.Crc32;
  1570.                   end {if};
  1571.                   LZW_Cleanup;
  1572.                end {1 = shrunk};
  1573.       2..5  :  Begin
  1574.                   If Init_UnReduce then
  1575.                      UnReduce
  1576.                   else begin
  1577.                      Writeln('Not enough memory available to unreduce!');
  1578.                      Writeln('Skipping ', Hdr_FileName, ' ...');
  1579.                      FSkip(LocalHdr.Compressed_Size);
  1580.                      Crc32Val := NOT LocalHdr.Crc32;
  1581.                   end {if};
  1582.                   Cleanup_UnReduce;
  1583.                end {2..5};
  1584.          6  :  Begin
  1585.                   If Init_Explode then
  1586.                      Explode
  1587.                   else begin
  1588.                      Writeln('Not enough memory available to unimplode!');
  1589.                      Writeln('Skipping ', Hdr_FileName, ' ...');
  1590.                      FSkip(LocalHdr.Compressed_Size);
  1591.                      Crc32Val := NOT LocalHdr.Crc32;
  1592.                   end {if};
  1593.                   Cleanup_Explode;
  1594.                end {6};
  1595.       else     Begin
  1596.                   Writeln('Unknown compression method used on ', Hdr_FileName);
  1597.                   Writeln('Skipping ', Hdr_FileName, ' ...');
  1598.                   FSkip(LocalHdr.Compressed_Size);
  1599.                   Crc32Val := NOT LocalHdr.Crc32;
  1600.                end {else};
  1601.    end {case};
  1602.  
  1603.    Crc32Val := NOT Crc32Val;
  1604.    If Crc32Val <> LocalHdr.Crc32 then begin
  1605.       Writeln;
  1606.       Writeln('WARNING: File ', OutPath + Hdr_FileName, ' fails CRC check!');
  1607.       Writeln('   Stored CRC = ', HexLInt(LocalHdr.Crc32),
  1608.               '   Calculated CRC = ', HexLInt(Crc32Val));
  1609.    end {if};
  1610.  
  1611. end {UnZip};
  1612.  
  1613. { --------------------------------------------------------------------------- }
  1614.  
  1615. Procedure Extract_File;
  1616. Var
  1617.    YesNo  : Char;
  1618.    DosDTA : SearchRec;
  1619. Label
  1620.    Exit;
  1621. Begin
  1622.    FindFirst(OutPath + Hdr_FileName, ANYFILE, DosDTA);
  1623.    If DosError = 0 then begin
  1624.       Write('WARNING: ', OutPath + Hdr_FileName, ' already exists.  Overwrite (Y/N)? ');
  1625.       YesNo := ReadKey;
  1626.       Writeln(YesNo);
  1627.       If UpCase(YesNo) <> 'Y' then begin
  1628.          FSkip(LocalHdr.Compressed_Size);
  1629.          Goto Exit;
  1630.       end {if};
  1631.    end {if};
  1632.  
  1633.    If Open_Ext then begin
  1634.       Write('Extracting: ', OutPath + Hdr_FileName, ' ...    ');
  1635.       UnZip;
  1636.       GotoXY(WhereX - 4, WhereY);
  1637.       ClrEol;
  1638.       Writeln(' done');
  1639.       Close_Ext;
  1640.    end {then}
  1641.    else begin
  1642.       Writeln('Could not open output file ', OutPath + Hdr_FileName, '!  Skipping to next file ...');
  1643.       FSkip(LocalHdr.Compressed_Size);
  1644.    end {If};
  1645. Exit:
  1646. end {Extract_File};
  1647.  
  1648. { --------------------------------------------------------------------------- }
  1649.  
  1650. Procedure Extract_Zip;
  1651. Var
  1652.    Match : Boolean;
  1653.    I     : Word;
  1654. Begin
  1655.    Open_Zip;
  1656.    While Read_Local_Hdr do begin
  1657.       Match := FALSE;
  1658.       I := 1;
  1659.       Repeat
  1660.          If SameFile(InFileSpecs[I], Hdr_FileName) then
  1661.             Match := TRUE;
  1662.          Inc(I);
  1663.       Until Match or (I > MaxSpecs);
  1664.       If Match then
  1665.          Extract_File
  1666.       else
  1667.          FSkip(LocalHdr.Compressed_Size);
  1668.    end {while};
  1669.    Close_Zip;
  1670. end;
  1671.  
  1672. { --------------------------------------------------------------------------- }
  1673.  
  1674. Begin
  1675.    Assign(Output, '');
  1676.    Rewrite(Output);
  1677.    Writeln;
  1678.    Writeln(COPYRIGHT);
  1679.    Writeln(VERSION);
  1680.    Writeln;
  1681.    Load_Parms;   { get command line parameters }
  1682.    Initialize;   { one-time initialization }
  1683.    Extract_Zip;  { de-arc the file }
  1684. end.
  1685.